using AnimCollections;
using AOT;
using Unity.Burst;
using UnityEngine.Animations;
using UnityEngine.Playables;

namespace DA.AnimGraph {
    [BurstCompile]
    public unsafe partial struct SequenceNode : INode {
        public struct ClipSlice {
            public float offset;
            public float start; // 在整个时间轴上的时间
            public float end; // 在整个时间轴上的时间
        }

        public struct SetupData : INodeSetupData {
            public FloatLinkDesc playbackSpeedDesc;
            public ClipLinkDesc[] clipsDesc;
            public ClipSlice[] clipSlices; // 没有传入数据就按收尾相接处理
            public int childrenCount => clipsDesc.Length;
        }

        //
        private int childrenCount;
        private NativeSimpleArray<ClipLink> linkChildClips;
        private NativeSimpleArray<ClipSlice> clipSlices; // 需要按先后顺序排序
        private NativeSimpleArray<float> weights;
        private NativeSimpleArray<AnimationClipPlayable> childPlayables;
        private FloatLink playbackSpeed;

        //
        private AnimationMixerPlayable playable;
        private TimeState timeState;

        // 描述当前的区间，
        private int curIndex; // 表示当前的动作序号，如果处在过渡中，则为旧的
        private bool isInTransition; // 是否在过渡中，和curIndex配合表示阶段。例如(0,false)之后是(0,true)此时处于淡出，完全淡出后是(1,false)
        fixed float playbackTime[2]; // 当前动作和下一个动作的时间（如果不在过渡则为0）

        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.DisposeDelegate))]
        private static bool Dispose(in void* node) {
            var self = (SequenceNode*)node;

            for (int i = 0; i < self->childPlayables.Length; i++) {
                self->childPlayables[i].Destroy();
            }
            self->childPlayables.Dispose();

            self->linkChildClips.Dispose();
            self->clipSlices.Dispose();
            self->weights.Dispose();

            //self->playable.Destroy();
            return false;
        }

        private void Setup(in SetupData data, ref GraphBuffer buffer, in NodeLink* link) {
            childrenCount = data.childrenCount;
            playbackSpeed = new FloatLink(data.playbackSpeedDesc);

            buffer.MallocNativeSimpleArray(out linkChildClips, childrenCount);
            for (int i = 0; i < data.childrenCount; i++) {
                linkChildClips[i] = new(data.clipsDesc[i]);
            }

            if (data.clipSlices != null) {
                buffer.MallocNativeSimpleArray(out clipSlices, data.clipSlices);
                timeState.SetDuration(1); // timeState 还未正式启用，借用此字段记录是否写入了 clipSlices 数据，1为已写入
            } else {
                // 没有传入数据就按首尾相接处理，需要各片段时长，只能在后面处理
                buffer.MallocNativeSimpleArray(out clipSlices, childrenCount);
                timeState.SetDuration(0);
            }

            buffer.MallocNativeSimpleArray(out weights, childrenCount);
            buffer.MallocNativeSimpleArray(out childPlayables, childrenCount);
        }

        private static Playable BuildPlayableGraph(in void* node, in NodeLink* link, in PlayableGraph graph, in BuildContext buildContext) {
            var self = (SequenceNode*)node;

            bool noClipSlices = self->timeState.duration == 0;
            self->timeState = new(TimeState.AccumulateMode.Time);
            link->SetTimeState(self->timeState);

            int childrenCount = self->childrenCount;
            self->playable = AnimationMixerPlayable.Create(graph, childrenCount);
            for (int i = 0; i < childrenCount; i++) {
                ref var clipLink = ref self->linkChildClips.GetRefAt(i);
                var animClip = clipLink.Get(buildContext);
                self->childPlayables[i] = AnimationClipPlayable.Create(graph, animClip);
                self->childPlayables[i].SetApplyFootIK(false);
                self->playable.ConnectInput(i, self->childPlayables[i], 0);
            }

            // 没有传入数据就按首尾相接处理
            if (noClipSlices) {
                float startTime = 0;
                float endTime = 0;
                for (int i = 0; i < childrenCount; i++) {
                    endTime += self->linkChildClips[i].duration;
                    ref var slice = ref self->clipSlices.GetRefAt(i);
                    slice.start = startTime;
                    slice.offset = 0;
                    slice.end = endTime;
                    startTime = endTime;
                }
                self->timeState.SetDuration(endTime);
            } else {
                var endTime = self->clipSlices[^1].end;
                self->timeState.SetDuration(endTime);
            }

            return self->playable;
        }

        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.EvaluateDelegate))]
        private static void Evaluate(in void* node, bool onEnter, in EvaluateContext evaluateContext, in GraphContext graphContext, ref UpstreamData upstream, ref ScopeContext scope) {
            var self = (SequenceNode*)node;

            int childrenCount = self->childrenCount;
            var playbackSpeed = self->playbackSpeed.Evaluate(onEnter, evaluateContext, graphContext, scope);
            self->timeState.Accumulate(evaluateContext.deltaTime * playbackSpeed);
            var time = self->timeState.time;
            NodeUtility.ClearWeights(ref self->weights, childrenCount);

            if (childrenCount > 1) {
                self->curIndex = -1;
                for (int i = 0; i < childrenCount - 1; i++) {
                    // 注意:这里没有考虑片段0和1之间断开的情况，比如（0~1）（2~3）两个片段中间就是断开的。希望在构建数据时排除这种可能
                    // 会导致下面这个if进不去，提前进入“完全在最后一段”分支，playbackTime会计算出负数
                    if (time >= self->clipSlices[i].start && time < self->clipSlices[i].end) {
                        if (time < self->clipSlices[i + 1].start) {
                            // 完全处于i上
                            self->weights[i] = 1;
                            self->isInTransition = false;
                            self->playbackTime[0] = time - self->clipSlices[i].start + self->clipSlices[i].offset;
                            self->playbackTime[1] = 0;
                        } else {
                            // 处于i到i+1的过渡
                            var transitionTime = time - self->clipSlices[i + 1].start;
                            var transitionDuration = self->clipSlices[i].end - self->clipSlices[i + 1].start;
                            var w = transitionTime / transitionDuration;
                            self->weights[i] = 1 - w;
                            self->weights[i + 1] = w;
                            self->isInTransition = true;
                            self->playbackTime[0] = time - self->clipSlices[i].start + self->clipSlices[i].offset;
                            self->playbackTime[1] = time - self->clipSlices[i + 1].start + self->clipSlices[i + 1].offset;
                        }
                        self->curIndex = i; //
                        break;
                    }
                }
                if (self->curIndex < 0) {
                    // 完全在最后一段（已经结束过渡）
                    int index = childrenCount - 1;
                    self->curIndex = index;
                    self->isInTransition = false;
                    self->weights[index] = 1;
                    self->playbackTime[0] = time - self->clipSlices[index].start + self->clipSlices[index].offset;
                    self->playbackTime[1] = 0;
                }
            } else {
                // 只包含1个动画
                self->curIndex = 0;
                self->isInTransition = false;
                self->weights[0] = 1;
                self->playbackTime[0] = time + self->clipSlices[0].offset;
                self->playbackTime[1] = 0;
            }
        }

        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.ApplyDelegate))]
        private static void Apply(in void* node) {
            var self = (SequenceNode*)node;

            int childrenCount = self->childrenCount;
            if (childrenCount == 0) return;

            for (int i = 0; i < self->childPlayables.Length; i++) {
                self->playable.SetInputWeight(i, self->weights[i]);
            }
            self->childPlayables[self->curIndex].SetTime(self->playbackTime[0]);
            if (self->isInTransition) {
                self->childPlayables[self->curIndex + 1].SetTime(self->playbackTime[1]);
            }
        }
    }
}
